use nand2tetris_vmtranslator_rust::data::{ArithmeticCommand, CommandType, Segment};

use crate::compiler::data::{ArithmeticOperator, KeywordConst, SubroutineType};

/// VM commands writer
#[derive(Debug, Default)]
pub(super) struct VmWriter {
    /// the VM code
    code: Vec<String>,
    /// subroutine code cache
    cache: Vec<String>,
}

impl VmWriter {
    /// clear the subroutine cache
    pub(super) fn init_subroutine_cache(&mut self) {
        if !self.cache.is_empty() {
            self.cache = vec![];
        }
    }
    fn write_cache_to_code(&mut self) {
        self.code.extend_from_slice(&self.cache);
    }
    fn to_command(command_type: CommandType, segment: Segment, index: u16) -> String {
        [
            command_type.to_string(),
            segment.to_string(),
            index.to_string(),
        ]
        .join(" ")
    }
    fn add_standard_command(&mut self, command_type: CommandType, segment: Segment, index: u16) {
        self.cache
            .push(Self::to_command(command_type, segment, index));
    }

    /// write a VM push command
    pub(super) fn write_push(&mut self, segment: Segment, index: u16) {
        self.add_standard_command(CommandType::Push, segment, index);
    }

    /// write a VM pop command
    pub(super) fn write_pop(&mut self, segment: Segment, index: u16) {
        self.add_standard_command(CommandType::Pop, segment, index);
    }

    /// write a VM arithmetic-logical command
    pub(super) fn write_arithmetic_command(&mut self, command: ArithmeticCommand) {
        self.cache.push(command.to_string());
    }

    /// write a VM label command
    pub(super) fn write_label(&mut self, label: &str) {
        self.cache.push(format!("{} {label}", CommandType::Label));
    }

    /// write a VM goto command
    pub(super) fn write_goto(&mut self, label: &str) {
        self.cache.push(format!("{} {label}", CommandType::Goto));
    }

    /// write a VM if command
    pub(super) fn write_if_goto(&mut self, label: &str) {
        self.cache.push(format!("{} {label}", CommandType::IfGoto));
    }

    /// write a VM call command
    ///
    /// # arguments
    ///
    /// * function_name : the called function name
    /// * num_args : the function's arguments num
    pub(super) fn write_call(&mut self, function_name: String, num_args: u16) {
        self.cache
            .push(format!("{} {function_name} {num_args}", CommandType::Call));
    }
    /// write a VM function command into the cache start
    ///
    ///
    /// # arguments
    ///
    /// * name : the function name
    /// * num_vars : the local variables num
    /// * subroutine_type : the subroutine type
    ///   * if type is method, auto append this align code
    /// * num_fields : the field variables num
    ///
    /// # remark
    ///
    /// after the call,will write cache to code.
    pub(super) fn write_function(
        &mut self,
        name: String,
        num_vars: u16,
        subroutine_type: SubroutineType,
        num_fields: u16,
    ) {
        let mut function_code = format!("{} {name} {num_vars}", CommandType::Function);
        if subroutine_type == SubroutineType::Method {
            // align this
            function_code.push('\n');
            function_code.push_str(&Self::to_command(CommandType::Push, Segment::Argument, 0));
            function_code.push('\n');
            function_code.push_str(&Self::to_command(CommandType::Pop, Segment::Pointer, 0));
        } else if subroutine_type == SubroutineType::Constructor && num_fields > 0 {
            // alloc the memory for this
            function_code.push('\n');
            function_code.push_str(&Self::to_command(
                CommandType::Push,
                Segment::Constant,
                num_fields,
            ));
            function_code.push_str(&format!("\n{} Memory.alloc 1", CommandType::Call));
            function_code.push('\n');
            function_code.push_str(&Self::to_command(CommandType::Pop, Segment::Pointer, 0));
        }
        self.cache.insert(0, function_code);
        self.write_cache_to_code();
    }

    /// write a VM function command
    pub(super) fn write_return(&mut self) {
        self.cache.push("return".into());
    }
    /// write a keyword const
    pub(super) fn write_keyword_const(&mut self, keyword_const: KeywordConst) {
        match keyword_const {
            KeywordConst::True => {
                self.write_push(Segment::Constant, 1);
                self.write_arithmetic_command(ArithmeticCommand::Neg);
            }
            KeywordConst::False => self.write_push(Segment::Constant, 0),
            KeywordConst::Null => self.write_push(Segment::Constant, 0),
            KeywordConst::This => self.write_push(Segment::Pointer, 0),
        }
    }

    /// write a string const
    pub(super) fn write_string_const(&mut self, value: String) {
        // push string's len
        self.write_push(Segment::Constant, value.len() as u16);
        // call String.new
        self.write_call("String.new".into(), 1);
        // append char to the new string
        value.chars().for_each(|c| {
            self.write_push(Segment::Constant, c as u16);
            self.write_call("String.appendChar".into(), 2);
        });
    }
    /// write a VM arithmetic orperator
    pub(super) fn write_arithmetic_operator(&mut self, op: ArithmeticOperator) {
        match op {
            ArithmeticOperator::Plus => self.write_arithmetic_command(ArithmeticCommand::Add),
            ArithmeticOperator::Minus => self.write_arithmetic_command(ArithmeticCommand::Sub),
            ArithmeticOperator::Multiply => self.write_call("Math.multiply".into(), 2),
            ArithmeticOperator::Divide => self.write_call("Math.divide".into(), 2),
            ArithmeticOperator::And => self.write_arithmetic_command(ArithmeticCommand::And),
            ArithmeticOperator::Or => self.write_arithmetic_command(ArithmeticCommand::Or),
            ArithmeticOperator::LessThan => self.write_arithmetic_command(ArithmeticCommand::Lt),
            ArithmeticOperator::GreaterThan => self.write_arithmetic_command(ArithmeticCommand::Gt),
            ArithmeticOperator::Equal => self.write_arithmetic_command(ArithmeticCommand::Eq),
            ArithmeticOperator::Not => self.write_arithmetic_command(ArithmeticCommand::Not),
        }
    }
    /// get the vm code
    pub(super) fn vm_code(&self) -> String {
        self.code.join("\n")
    }
}
